var assert          =   require("assert")
var TimeProvider    =   require("../util/timeProvider");
var XError          =   require("../util/x-errors-const");
var RpcCallError    =   require("../util/rpc-call-error");
var XQueue          =   require("../util/xqueue");
var TCPTransceiver  =   require("./transceiver").TCPTransceiver;
var UDPTransceiver  =   require("./transceiver").UDPTransceiver;

var AdapterProxy    =   function() {
    this._worker    =   undefined;
    this._endpoint  =   undefined;
    this._pTrans    =   undefined;
    this._pTimeoutQueueY    =   new XQueue();
    this._pTimeoutQueueN    =   new XQueue();
    this._activeStatus      =   true;
    this._nextFinishInvokeTime  = 0;
    this._nextRetryTime         = 0;
    this._frequenceFailTime     = 0;
    this._frequenceFailInvoke   = 0;
    this._totalInvoke           = 0;
    this._timeoutInvoke         = 0;
}

module.exports  =   AdapterProxy;

AdapterProxy.prototype.__defineGetter__("worker", function() { return this._worker; })
AdapterProxy.prototype.__defineSetter__("worker", function(value) { this._worker = value; })

AdapterProxy.prototype.__defineGetter__("endpoint", function() { return this._endpoint; })
AdapterProxy.prototype.__defineSetter__("endpoint", function(value) { this._endpoint = value; })

AdapterProxy.prototype.pushTimeoutQueueN    =   function(reqMessage) {
    this._pTimeoutQueueN.push(reqMessage.request.iRequestId, reqMessage);
}

AdapterProxy.prototype.pushTimeoutQueueY    =   function(reqMessage) {
    this._pTimeoutQueueY.push(reqMessage.request.iRequestId, reqMessage);
}

AdapterProxy.prototype.initialize   =   function() {
    assert(this._endpoint.sProtocol === "tcp" || this._endpoint.sProtocol === "udp", "trans protocol must be tcp or udp");
    if(this._endpoint.sProtocol === "tcp") {
        this._pTrans    =   new TCPTransceiver(this, this._endpoint);
    } else {
        this._pTrans    =   new UDPTransceiver(this, this._endpoint);
    }
}

AdapterProxy.prototype._resetQueue  =   function() {
    var self = this;
    var worker = self._worker;
    self._pTimeoutQueueY.forEach(function(key) {
        var reqMessage  =   self._pTimeoutQueueY.pop(key, true);
        delete reqMessage.adapter;
        delete reqMessage.RemoteEndpoint;
        worker.pTimeoutQueue.push(reqMessage.request.iRequestId, reqMessage);
    });

    self._pTimeoutQueueN.forEach(function(key) {
        var reqMessage  =   self._pTimeoutQueueN.pop(key, true);
        delete  reqMessage.adapter;
        delete  reqMessage.RemoteEndpoint;
        worker.pTimeoutQueue.push(reqMessage.request.iRequestId, reqMessage);
    })

    process.nextTick(function() {
        worker.doInvoke();
    })
}

AdapterProxy.prototype.destroy  =   function() {
    this._resetQueue();
    this._pTrans.close();
}

AdapterProxy.prototype.doResponse   =   function(rspMessage) {
    var reqMessage  =   this._pTimeoutQueueY.pop(rspMessage.iRequestId, true);
    if(reqMessage === undefined) {
        return;
    }
    reqMessage.LocalEndpoint    =   this._pTrans.LocalEndpoint;
    reqMessage.clearTimeout();
    reqMessage.adapter.finishInvoke(rspMessage.iResultCode, reqMessage);
    if(rspMessage.iResultCode ===0) {
        reqMessage.promise.resolve({request:reqMessage, response:rspMessage.origin});
    } else {
        reqMessage.promise.reject(new RpcCallError({request:reqMessage, response:rspMessage.origin, error:{code:rspMessage.iResultCode, message:rspMessage.sResultDesc}}));
    }
}

AdapterProxy.prototype.doTimeout    =   function(reqMessage) {
    this._pTimeoutQueueY.erase(reqMessage.request.iRequestId);
    this._pTimeoutQueueN.erase(reqMessage.request.iRequestId);
    reqMessage.clearTimeout();
    reqMessage.adapter.finishInvoke(XError.CLIENT.REQUEST_TIMEOUT, reqMessage);
    reqMessage.promise.reject(new RpcCallError({request:reqMessage, response:undefined, error:{code:XError.CLIENT.REQUEST_TIMEOUT, message:"call remote server timeout(remote server no response"}}));
}

AdapterProxy.prototype.doInvoke =   function() {
    var self = this;
    self._pTimeoutQueueN.forEach(function(key) {
        var reqMessage = self._pTimeoutQueueN.pop(key, true);
        if(self._pTrans.sendRequest(reqMessage.request)) {
            self._pTimeoutQueueY.push(reqMessage.request.iRequestId, reqMessage);
        } else {
            self._doInvokeException(reqMessage);
        }
    });

    self._worker.doInvoke();
}

AdapterProxy.prototype._doInvokeException   =   function(reqMessage) {
    reqMessage.clearTimeout();
    reqMessage.adapter.finishInvoke(XError.CLIENT.ADAPTER_NOT_FOUND, reqMessage);
    reqMessage.promise.reject(new RpcCallError({request:reqMessage, response:undefined, error:{code:XError.CLIENT.ADAPTER_NOT_FOUND, message:"call remote server error(send again error)"}}));
}

AdapterProxy.prototype.invoke   =   function(reqMessage) {
    reqMessage.adapter  = this;
    reqMessage.RemoteEndpoint   =   this._endpoint;
    if(this._pTrans.sendRequest(reqMessage.request)) {
        this._pTimeoutQueueY.push(reqMessage.request.iRequestId, reqMessage);
    } else {
        this._pTimeoutQueueN.push(reqMessage.request.iRequestId, reqMessage);
    }
}

AdapterProxy.prototype._setInactive =   function() {
    this._activeStatus  =   false;
    this._nextRetryTime =   TimeProvider.nowTimestamp() + this._worker._checkTimeoutInfo.tryTimeInterval;
    this._pTrans.close();
}

AdapterProxy.prototype.checkActive  =   function(bForceConnect) {
    if(this._pTrans.hasConnected() || this._pTrans.isConnecting()) {
        return true;
    }
    var nowTime =   TimeProvider.nowTimestamp();
    if(bForceConnect) {
        this._nextRetryTime =   nowTime + this._worker._checkTimeoutInfo.tryTimeInterval;
        if(!this._pTrans.isValid()) {
            this._pTrans.reconnect();
        }
        return this._pTrans.hasConnected() || this._pTrans.isConnecting();
    }
    if(!this._activeStatus && nowTime < this._nextRetryTime) {
        return false;
    }
    if(!this._pTrans.isValid()) {
        this._pTrans.reconnect();
    }

    if(!this._activeStatus) {
        this._nextRetryTime =   nowTime + this._worker._checkTimeoutInfo.tryTimeInterval;
    }

    return this._pTrans.hasConnected() || this._pTrans.isConnecting();
}

AdapterProxy.prototype._doFinishInvoke  =   function(iResultCode) {
    var nowTime =   TimeProvider.nowTimestamp();
    if(this._activeStatus === false) {
        if(iResultCode === 0) {
            this._activeStatus  =   true;
            this._frequenceFailInvoke   = 0;
            this._totalInvoke           = 1;
            this._timeoutInvoke         = 0;
            this._nextFinishInvokeTime  = nowTime + this._worker._checkTimeoutInfo.checkTimeoutInterval;
        } else {
            console.info("[AdapterProxy::finishInvoke(bool), ", this._worker.name, ", ", this._endpoint.toString(), ". retry fail]");
        }
        return;
    }

    this._totalInvoke++;
    
    if(iResultCode !== 0) {
        this._timeoutInvoke++;
        if(this._frequenceFailInvoke === 0) {
            this._frequenceFailTime = nowTime + this._worker._checkTimeoutInfo.minFrequenceFailTime;
        }
        this._frequenceFailInvoke++;
        if(this._frequenceFailInvoke >= this._worker._checkTimeoutInfo.frequenceFailInvoke && nowTime >= this._frequenceFailTime) {
            this._setInactive();
        }
    } else {
        this._frequenceFailInvoke = 0;
    }
    if(nowTime > this._nextFinishInvokeTime) {
        this._nextFinishInvokeTime  =   nowTime + this._worker._checkTimeoutInfo.checkTimeoutInterval;
        if(iResultCode !==0 && this._timeoutInvoke >= this._worker._checkTimeoutInfo.minTimeoutInvoke && this._timeoutInvoke >= parseInt(this._worker._checkTimeoutInfo.radio * this._totalInvoke)) {
            this._setInactive();
        } else {
            this._totalInvoke = 0;
            this._timeoutInvoke =   0;
        }
    }
}

AdapterProxy.prototype.finishInvoke =   function(iResultCode, reqMessage) {
    this._doFinishInvoke(iResultCode);
}